"""
Created on 28-Oct-2009

@author: dexen
"""


import time
import rpyc
import threading

from dexen.system.base import service
from dexen.system.server.naming_manager import NamingManager
#===============================================================================
# Constants
#===============================================================================
ALIVE = "Alive"
DEAD = "Dead"
PENDING = "Pending"

INF = 100000
#===============================================================================
# Structural Classes
#===============================================================================
class NodeInfo(object):
    def __init__(self, name, max_slots=4):
        self.name = name
        self.masters = []
        self.slaves = []
        self.max_slots = max_slots
        self.status = ALIVE
        self.conn = rpyc.connect_by_service(self.name, 
                                            config=service.default_config)
    
    def add_master(self, name):   
        self.conn.root.create_master(name)
        self.masters.append(name)
        return MasterInfo(name)
    
    def add_slave(self, mastername, name):
        self.conn.root.create_slave(mastername, name)
        self.slaves.append(name)
        return SlaveInfo(name)
    
    def no_free_slots(self):
        return self.max_slots - len(self.masters) - len(self.slaves)
    
    def has_free_slots(self):
        return self.max_slots > (len(self.masters) + len(self.slaves))
    
    def is_alive(self):
        return self.status == ALIVE

    def __repr__(self):
        ret = ""
        for name in self.slaves:
            ret += "    %s\n" %(name)
        for name in self.masters:
            ret += "    %s\n" % (name)
        return ret
    
class MasterInfo(object):
    def __init__(self, name):
        self.name = name
        self.status = ALIVE
        self.slaves = []
        self.conn = rpyc.connect_by_service(name,
                                            config=service.default_config)
    
    def run_schema(self, spec):
        self.conn.root.run_schema(spec)
        
    def add_slave(self, name):
        self.conn.root.add_slaves([name])
        self.slaves.append(name)

    def num_slaves(self):
        return len(self.slaves)
    
    def update(self):
        #master_info, slaves_info = self.conn.root.get_info()
        #slaves_info = rpyc.classic.obtain(slaves_info)  
        #return slaves_info
        return None


class SlaveInfo(object):
    def __init__(self, name):
        self.name = name
        self.status = ALIVE
    
    def crashed(self):
        self.status = DEAD
    
    def is_crashed(self):
        return self.status == DEAD
    
#===============================================================================
# Manager Class
#===============================================================================
class UpdateManager(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.setDaemon(True)
        self.name = "UpdateManager"
              
        self.nodes = {}
        self.masters = {}
        self.slaves = {}
         
        self.master_namer = NamingManager("MASTER")    
        self.slave_namer = NamingManager("SLAVE")    
        
        self.lock = threading.RLock()

    
    def run(self):
        self.node_reg = rpyc.connect_by_service("NODEREGISTRY")

        while True:
            new_nodes, dead_nodes = self.node_reg.root.get_nodes()
            new_nodes = rpyc.classic.obtain(new_nodes)
            dead_nodes = rpyc.classic.obtain(dead_nodes)
            print "New nodes and dead nodes"
            print new_nodes, dead_nodes
            with self.lock:
                self.update_nodes(new_nodes.keys(), dead_nodes.keys())
                self.update_masters()
                self.create_slaves()
                print "InfoMgr\n", self
            time.sleep(5)               
            
    #===========================================================================
    # 
    #===========================================================================
    def update_nodes(self, new_nodes, dead_nodes):
        for name in dead_nodes:
            self.nodes[name].status = DEAD
        for name in new_nodes:
            self.add_node(name)

    def add_node(self, name):
        self.nodes[name] = NodeInfo(name)

    def get_available_node(self):
        for node in self.nodes.values():
            if node.has_free_slots() and node.is_alive():
                return node       
                  
    #===========================================================================
    # 
    #===========================================================================
    def run_master(self, spec):
        with self.lock:
            name = self.master_namer.get_name()
            self.add_master(name)
            self.masters[name].run_schema(spec)
    
    def add_master(self, name):
        node = self.get_available_node()
        master_info = node.add_master(name)
        self.masters[name] = master_info
        
    #===========================================================================
    # 
    #===========================================================================
    def update_masters(self):
        """
        for name in self.masters.keys():
            master_info = self.masters[name]
            slaves_info = master_info.update()
            for slave_info in slaves_info:
                self.slaves[slave_info.name] = slave_info
        """
        pass
    
    def get_master(self, name):
        return self.masters[name]
    
    def get_masters(self):
        return self.masters.values()
    
    #===========================================================================
    # 
    #===========================================================================
    def create_slaves(self):
        cur = None
        min = INF
        for name in self.masters.keys():
            master_info = self.masters[name]
            if master_info.num_slaves() < min:
                min = master_info.num_slaves()
                cur = master_info
        if not cur: return
        
        node = self.get_available_node()
        if not node: return
        
        name = self.slave_namer.get_name()
        slave_info = node.add_slave(cur.name, name)
        self.slaves[name] = slave_info
        cur.add_slave(name)  
    
    def add_slave(self, name, nodename, mastername):
        self.slaves[name] = SlaveInfo(name)
        self.nodes[nodename].add_slave(name)
        self.masters[mastername].add_slave(name)
        
    #===========================================================================
    # 
    #===========================================================================
    def __repr__(self):
        t = ""
        for name, node in self.nodes.items():
            t += "%s:\n%r\n"%(name, node)
        return t
